import java.awt.*;
import java.awt.event.*;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.util.ArrayList;

import javax.swing.*;

/**
 * Graphical user interface to a Tic Tac Toe application.
 * The GUI is incomplete because it has no associated player.
 * It also needs a routine for checking if one of the players
 * have got five marks in a row.
 */
public class TicTacToeGui extends JFrame implements Constants, ActionListener {
	/** Textfield showing what mark you use ('X' or 'O') */
	private JTextField id;
	/** TextArea giving feedback to the user */
	private TextArea display;
	/** The panel containing the board */
	private JPanel boardPanel;
	/** The squares of the board */
	private Square board[][];
	/** The menu bar */
	private JMenuBar menuBar;
	/** The game submenu */
	private JMenu gameMenu;
	/** Game submenu choices */
	private JMenuItem newGameItem, quitItem,connectItem;
	
	/** The name of the player using this GUI */
	private String myName;
	/** The mark used by this player ('X' or 'O') */
	private char myMark;
	private boolean isPlaying;
	private boolean isFinished=false;
	private int turns=0;
	private ArrayList<int[]>winning_positions=new ArrayList<int[]>();
	private ITicTacPlayer otherPlayer;
	private TicTacPlayer parent;
	
	/**
	 * Creates a new GUI.
	 * @param name	The name of that player.
	 * @param mark	The mark used by that player.
	 */
	public TicTacToeGui(TicTacPlayer prnt) {
		this.parent=prnt;
		this.myName=(String)JOptionPane.showInputDialog(null, "What is your name?", "Jhonny");
		isPlaying=false;
		// Create GUI components:
		// The display at the bottom:
		display = new TextArea("", 4, 30, TextArea.SCROLLBARS_VERTICAL_ONLY);
		display.setEditable(false);
		// The name field at the top:
		id = new JTextField();
		id.setEditable(false);
		id.setText("Hey "+ myName+"!");
		// The board:
		JPanel gridPanel = new JPanel();
		gridPanel.setLayout(new GridLayout(BOARD_SIZE, BOARD_SIZE, 0, 0));
		board = new Square[BOARD_SIZE][BOARD_SIZE];
		for(int row = 0; row < board.length; row++) 
			for(int col = 0; col < board[row].length; col++) {
				board[row][col] = new Square(this, row, col);
				gridPanel.add(board[row][col]);
			}
		boardPanel = new JPanel();
		boardPanel.add(gridPanel);

		// Place the components:
		Container cp = getContentPane();
		cp.setLayout(new BorderLayout());
		cp.add("South", display);
		cp.add("North", id);
		cp.add("Center", boardPanel);

		// Create the menu.
		menuBar = new JMenuBar();
		setJMenuBar(menuBar);
		gameMenu = new JMenu("Game");
		gameMenu.setMnemonic(KeyEvent.VK_G);
		menuBar.add(gameMenu);
		newGameItem = new JMenuItem("New game", KeyEvent.VK_N);
		newGameItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0));
		
		connectItem=new JMenuItem("Connect",KeyEvent.VK_C);
		gameMenu.add(connectItem);
		gameMenu.add(newGameItem);
		quitItem = new JMenuItem("Quit", KeyEvent.VK_Q);
		quitItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, ActionEvent.ALT_MASK));
		gameMenu.add(quitItem);

		// Add listeners
		connectItem.addActionListener(this);
		newGameItem.addActionListener(this);
		newGameItem.setEnabled(false);
		quitItem.addActionListener(this);
		// Add an anonymous WindowListener which calls quit() when the window is closing
		addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				quit();
			}
		});
		
		// Place and show the window:
		setTitle("Tic Tac Toe: " + myName);
		setSize(WIN_WIDTH, WIN_HEIGHT);
		setLocation(200, 200);
		setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
		setVisible(true);
	}

	/**
	 * Called by the Square class when an empty square is clicked.
	 * @param row		The row of the square that was clicked.
	 * @param column	The column of the square that was clicked.
	 */
	public void squareClicked(int row, int column) {
		if(isPlaying&&!isFinished){
			turns++;
			if(otherPlayer!=null){
				try {
					otherPlayer.playerClicked(row, column); //Alerts the other player of this click
					setMark(row, column, myMark);
					if(!checkBoard())	//Check for winner
						setIsPlaying(otherPlayer);	
				} catch (RemoteException e) {
					showErrorMessage(e.getMessage());
				}
			}
		}
	}

	/**
	 * Marks the specified square of the board with the specified mark.
	 * @param row		The row of the square to mark.
	 * @param column	The column of the square to mark.
	 * @param mark		The mark to use.
	 */
	public void setMark(int row, int column, char mark) {
		board[row][column].setMark(mark);
		repaint();
	}

	/**
	 * Called when a menu item has been selected.
	 * @param e	The ActionEvent that occured.
	 */
	public void actionPerformed(ActionEvent e) {
		if(e.getSource() == newGameItem)
			newGame();
		else if(e.getSource() == quitItem)
			quit();
		if(e.getSource()==connectItem){
			initConnection();
		}
	}
	
	/**
	 * Tries to retrieve the remotePlayer object and if successful start the game 
	 * @param address Which address to connect to
	 * @param port Which port to connect to
	 */
	private void initConnection(){
		String address=requestString("Who do you want to connect to?", "localhost");
		int port=requestInt("Which port?", "3200");
		String url="//"+address+":"+port+"/Server"; //Name of the remote object 
		println("Waiting for "+url);
		try {
			otherPlayer=(ITicTacPlayer)Naming.lookup(url);	//Acquire the object associated with url	
			otherPlayer.initiate(parent);	//Connect to the found object			
				
		} catch (Exception e) {
			showErrorMessage("Could not connect to remote host, please try again\n"+e.getMessage());
		} 	
	}	


	
	/**
	 * Starts a new game, if the user confirms it in a dialog box.
	 */
	public void newGame() {
		if(JOptionPane.showConfirmDialog(null, "Are you sure you want to start a new game?", "Start over?", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
			if(otherPlayer!=null){
				try {
					otherPlayer.newGame();
				} catch (RemoteException e) {
					showErrorMessage(e.getMessage());
				}
			}else{
				initConnection();
			}
		}
	}

	public boolean showChallengedDialog(String name){
		return JOptionPane.showConfirmDialog(boardPanel, name+" Wants to challenge you, accept?", "Challenged", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION;		

	}
	
	/**
	 * Removes all marks from the board.
	 */
	public void clearBoard() {
		for(int row = 0; row < board.length; row++)
			for(int col = 0; col < board[row].length; col++){
				board[row][col].setMark(' ');
				board[row][col].setBackground(Color.WHITE);
			}
		repaint();
	}

	/**
	 * Exits the game, if the user confirms it in a dialog box.
	 * Should notify the opponent that we left the game.
	 */
	public void quit() {
		if(JOptionPane.showConfirmDialog(this, "Are you sure you want to quit?", "Really quit?", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
			if(otherPlayer!=null)
				try {
					otherPlayer.quit();
					
				} catch (RemoteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			System.exit(0);
		}
	}

	/**
	 * Outputs a message to the user.
	 * @param s	The string to display. Adds a newline to the end of the string.
	 */
	public void println(String s) {
		display.append(s + "\n");
	}

	/**
	 * Outputs a message to the user.
	 * @param s	The string to display.
	 */
	public void print(String s) {
		display.append(s);
	}
	
	public boolean isFinished(){
		return isFinished;
	}
	
	public void onFinished(){
		isPlaying=false;
		isFinished=true;
		newGameItem.setEnabled(true);
		parent.bind();

	}
	
	/**
	 * Called if the game is a draw
	 */
	public void onDraw(){
		println("IT IS A DRAW!");
		onFinished();
	}
	/**
	 * Called if the game has a winner
	 * @param c the winner
	 */
	public void onWin(char c){
		for(int[]i:winning_positions)
			board[i[0]][i[1]].setBackground(Color.GREEN);
		repaint();
		println((c==myMark?"You are":getOtherName()+" is")+" the winner!");
		onFinished();

		
	}
	
	/**
	 * @return The remote players name
	 */
	public String getOtherName() {
		String n="NaN";
		if(otherPlayer!=null){
			try {
				return otherPlayer.getName();
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
		return n;
	}
	
	/**
	 * Checks the board for a winner/draw
	 * @return
	 */
	public boolean checkBoard(){		
		boolean won=false;
		for(int x=0;x<Constants.BOARD_SIZE;x++){
			for(int y=0;y<Constants.BOARD_SIZE;y++){
				char p=board[x][y].getMark();
				if(p!=' '){
					if(x<Constants.BOARD_SIZE-Constants.WINNER_LENGTH){	//Horizontal check
						if(count(x, 1, y, 0))
							won=true;
						if(y>Constants.WINNER_LENGTH-1)	//Left to right bottom to top diagonal check  
							if(count(x,1,y,-1))
								won=true;
						}
					if(y<Constants.BOARD_SIZE-Constants.WINNER_LENGTH){	//Vertical check
						if(count(x,0,y,1))
							won=true;
						if(x<Constants.BOARD_SIZE-Constants.WINNER_LENGTH)	//Left to right top to bottom diagonal check
							if(count(x, 1, y, 1))
								won=true;
					}
					if(won){						
						onWin(p);
						return true;
					}
				}
			}
		}
		if(turns>=Constants.BOARD_SIZE*Constants.BOARD_SIZE)
			onDraw();
		return false;		
	}
	/**
	 * For the given check if we have a winner, also add the winning combination to winning_positions
	 * @param x start x position
	 * @param dx which x direction to move
	 * @param y	start y position
	 * @param dy which y direction to move
	 * @return
	 */
	private boolean count(int x, int dx,int y,int dy){
		char player=board[x][y].getMark();
		winning_positions.clear();
		for(int i=0;i<Constants.WINNER_LENGTH;i++){
			if(board[x+dx*i][y+dy*i].getMark()!=player)
				return false;
			winning_positions.add(new int[]{x+dx*i,y+dy*i});
		}
		return true;
	}
	
	public void setIsPlaying(ITicTacPlayer game){
		this.isPlaying=(game==parent);
		if(game!=null)
			println(game==parent?"Your turn":getOtherName()+"'s turn");
	}

	public int requestInt(String message,String def) {
		int b=-1;	
		try{
			b= Integer.parseInt(requestString(message, def));
		}catch(NumberFormatException e){}
		return b;
	}
	public String requestString(String message,String def){
		return JOptionPane.showInputDialog(this,message,def);
	}
	
	public void showErrorMessage(final String s){
		parent.bind();
		SwingUtilities.invokeLater(new Runnable() {
			
			@Override
			public void run() {
				JOptionPane.showMessageDialog(null,s,"An error occured",JOptionPane.ERROR_MESSAGE);
				
			}
		});
	}
	
	@Override
	public String getName() {
		return myName;
	}
	
	public void setMark(char mark){
		id.setText(myName+", "+"you are player "+mark);
		println("You are "+mark);
		myMark=mark;
	}
	public char getMark() {
		return myMark;
	}
	
	public void setOtherPlayer(ITicTacPlayer otherPlayer) {
		this.otherPlayer = otherPlayer;
	}
	
	public char getOtherMark(){
		try {
			return otherPlayer.getMark();
		} catch (RemoteException e) {
			return ' ';
		}
	}
	
	public ITicTacPlayer getOtherPlayer() {
		return otherPlayer;
	}
}
